package projet_v1.simulation;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
import java.awt.Color;
import java.awt.Dimension;

import projet_v1.Breed;
import projet_v1.Character;
import projet_v1.Actor;
import projet_v1.Human;
import projet_v1.Item;
import projet_v1.MadZombie;
import projet_v1.Vampire;
import projet_v1.Zombie;
import projet_v1.items.help.FirstAidKit;
import projet_v1.items.help.Food;
import projet_v1.items.weapons.LiquidNitrogen;
import projet_v1.items.weapons.Shotgun;
import projet_v1.items.weapons.WoodenStake;
import projet_v1.simulation.events.ChimicalAttack;
import projet_v1.simulation.events.Event;
import projet_v1.simulation.events.Rescue;

/**
 * A simple predator-prey simulator, based on a rectangular field containing
 * rabbits and foxes.
 * 
 * @author David J. Barnes and Michael Kolling
 * @version 2008.03.30
 */
public class Simulator {
    // Constants representing configuration information for the simulation.
    // The default width for the grid.
    private static final int DEFAULT_WIDTH = 50;
    // The default depth of the grid.
    private static final int DEFAULT_DEPTH = 50;
    // The probability that a fox will be created in any given grid position.
    private static final double VAMPIRE_CREATION_PROBABILITY = 0.02;// 0.04
    // The probability that a rabbit will be created in any given grid position.
    private static final double ZOMBIE_CREATION_PROBABILITY = 0.04;// 0.04
    // The probability that a rabbit will be created in any given grid position.
    private static final double MADZOMBIE_CREATION_PROBABILITY = 0.01;// 0.01
    // The probability that a rabbit will be created in any given grid position.
    private static final double HUMAN_CREATION_PROBABILITY = 0.4;
    // The nb of steps before the chimical attack government launch
    private static final int CHIMICAL_ATTACK_TIMER = 100;// 100
    // Every RESCUE_TIMER steps, some ammunitions, food or first-aid kits, are
    // sent
    private static final int RESCUE_TIMER = 1;

    // Lists of humans or monsters in the field. Separate lists are kept for
    // ease of
    // iteration.
    private List<Character> characters;
    // The current state of the field.
    private Field field;
    // A second field, used to build the next stage of the simulation.
    private Field updatedField;
    // The current step of the simulation.
    private int step;
    // A graphical view of the simulation.
    private SimulatorView view;
    // Particular events running
    private HashMap<Event, Integer> events;

    /**
     * Construct a simulation field with default size.
     */
    public Simulator() {
        this(DEFAULT_DEPTH, DEFAULT_WIDTH);
    }

    /**
     * Create a simulation field with the given size.
     * 
     * @param depth
     *            Depth of the field. Must be greater than zero.
     * @param width
     *            Width of the field. Must be greater than zero.
     */
    public Simulator(int depth, int width) {
        if (width <= 0 || depth <= 0) {
            System.out.println("The dimensions must be greater than zero.");
            System.out.println("Using default values.");
            depth = DEFAULT_DEPTH;
            width = DEFAULT_WIDTH;
        }
        characters = new ArrayList<Character>();
        field = new Field(depth, width);
        updatedField = new Field(depth, width);
        // Create a view of the state of each location in the field.
        view = new SimulatorView(depth, width);
        view.setColor(Zombie.class, Color.orange);
        view.setColor(MadZombie.class, Color.red);
        view.setColor(Vampire.class, Color.black);
        view.setColor(Human.class, Color.green);
        // Setup a valid starting point.
        reset();
        // Initialize a collection of events
        initEvent();

    }

    /**
     * @return the characters
     */
    public List<Character> getCharacters() {
        return characters;
    }

    /**
     * @return the step
     */
    public int getStep() {
        return step;
    }

    /**
     * @return the events
     */
    public HashMap<Event, Integer> getEvents() {
        return events;
    }

    /**
     * Run the simulation from its current state for a reasonably long period,
     * e.g. 500 steps.
     */
    public void runLongSimulation() {
        simulate(500);
    }

    /**
     * Run the simulation from its current state for the given number of steps.
     * Stop before the given number of steps if it ceases to be viable.
     * 
     * @param numSteps
     *            The number of steps to run for.
     */
    public void simulate(int numSteps) {
        for (int step = 1; step <= numSteps && view.isViable(field); step++) {
            simulateOneStep();
        }
    }

    /**
     * Run the simulation from its current state for a single step. Iterate over
     * the whole field updating the state of each fox and rabbit.
     * 
     * Modify by Romain Roufast
     */
    public void simulateOneStep() {
        step++;

        launchEvents();

        // Provide space for newborn characters.
        List<Actor> newCharacters = new ArrayList<Actor>();

        for (int i = 0; i < characters.size(); ++i) {
            Character c = characters.get(i);
            if (c != null) {
                // Remove dead characters from the simulation or transform them
                if (c.isAlive()) {
                    if (c.getBreed().equals(Breed.HUMAN)) {
                        // Look if he has been bitten
                        if (((Human) c).getHasBeenBitten()) {
                            Vampire newVampire = ((Human) c).turnIntoVampire();
                            characters.set(i, newVampire);
                            updatedField.place(newVampire, c.getLocation());
                        }
                    }

                    c.act(field, updatedField, newCharacters);

                } else {
                    // Look if he's dead beacause of a zombie
                    if (c.getBreed().equals(Breed.HUMAN)
                            && (c.getLastAttackedBy() != null)
                            && (c.getLastAttackedBy().equals(Breed.ZOMBIE))) {
                        if (Simulator.GenerateRandomBoolean()) {
                            if (c.getLocation() != null) {
                                Zombie newZombie = ((Human) c).turnIntoZombie();
                                characters.set(i, newZombie);
                                updatedField.place(newZombie, c.getLocation());
                            } else
                                characters.remove(i);
                        } else
                            characters.remove(i);
                    } else
                        characters.remove(i);
                }
            }
        }

        // Go to the next turn
        nextTurn();

        // Add new born characters to the main list of characters.
        characters.addAll((Collection<? extends Character>) newCharacters);

        // Swap the field and updatedField at the end of the step.
        Field temp = field;
        field = updatedField;
        updatedField = temp;
        updatedField.clear();
        // Display the new field on screen.
        view.showStatus(step, field);
    }

    /**
     * Reset the simulation to a starting position.
     */
    public void reset() {
        step = 0;
        characters.clear();
        field.clear();
        updatedField.clear();
        populate(field);
        // Show the starting state in the view.
        view.showStatus(step, field);
    }

    /**
     * Populate a field with foxes and rabbits.
     * 
     * @param field
     *            The field to be populated.
     */
    private void populate(Field field) {
        Random rand = new Random();
        field.clear();
        for (int row = 0; row < field.getDepth(); row++) {
            for (int col = 0; col < field.getWidth(); col++) {
                if (rand.nextDouble() <= VAMPIRE_CREATION_PROBABILITY) {
                    Location loc = new Location(row, col);
                    Vampire v = new Vampire(Vampire.HP_VAMPIRES, field, loc);
                    characters.add(v);
                    field.place(v, row, col);
                } else if (rand.nextDouble() <= HUMAN_CREATION_PROBABILITY) {
                    Location loc = new Location(row, col);
                    Human h = new Human(Human.HP_HUMANS, field, loc);
                    characters.add(h);
                    field.place(h, row, col);
                } else if (rand.nextDouble() <= ZOMBIE_CREATION_PROBABILITY) {
                    Location loc = new Location(row, col);
                    Zombie z = new Zombie(Zombie.HP_ZOMBIES, field, loc);
                    characters.add(z);
                    field.place(z, row, col);
                } else if (rand.nextDouble() <= MADZOMBIE_CREATION_PROBABILITY) {
                    Location loc = new Location(row, col);
                    MadZombie m = new MadZombie(MadZombie.HP_MADZOMBIES, field,
                            loc);
                    characters.add(m);
                    field.place(m, row, col);
                }
                // else leave the location empty.
            }
        }
        Collections.shuffle(characters);
    }

    /**
     * Perform all game logic for next turn.
     */
    public void nextTurn() {

        // Perform end-of-turn actions for all characters
        for (int i = 0; i < characters.size(); ++i) {
            Character c = characters.get(i);
            c.endOfTurn();
        }
    }

    /**
     * Initialize the list with particular events
     * 
     * @author Romain Roufast
     */
    private void initEvent() {

        events = new HashMap<Event, Integer>();
        events.put(new Rescue(new Location(30, 20)), RESCUE_TIMER);
        events.put(new ChimicalAttack(new Location(20, 30)),
                CHIMICAL_ATTACK_TIMER);
    }

    /**
     * Launch particular events
     *
     * @author Romain Roufast
     */
    private void launchEvents() {

        Iterator<Entry<Event, Integer>> iterator = events.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<Event, Integer> e = iterator.next();
            Event event = e.getKey();
            int stepToBegin = e.getValue();
            if (step >= stepToBegin) {
                configureLocation(event);
                boolean b = event.happend(characters, field);
                if (b == false)
                    iterator.remove();
            }

        }
    }

    /**
     * Choose the best place to launch the event
     * 
     * @author Romain Roufast
     */
    public void configureLocation(Event e) {

        int maxEffect = 0;
        Location bestLocation = new Location(20, 20);

        for (Character character : characters) {
            if (e.getTypeEvent() == Event.IS_GOOD_FOR_HUMANITY) {
                if (character.getBreed().equals(Breed.HUMAN)) {
                    Location loc = character.getLocation();
                    List<Location> l = field.adjacentLocations(loc, 1);
                    int tmpEffect = 0;
                    for (Location location : l) {
                        if (location != null) {
                            Character c = field.getCharacterAt(location);
                            if (c != null) {
                                if (((Character) c).getBreed().equals(
                                        Breed.ZOMBIE)
                                        || ((Character) c).getBreed().equals(
                                                Breed.VAMPIRE)
                                        || ((Character) c).getBreed().equals(
                                                Breed.MADZOMBIE)) {
                                    tmpEffect++;
                                }
                            }
                        }
                    }
                    if (tmpEffect > maxEffect) {
                        bestLocation = loc;
                        maxEffect = tmpEffect;
                    }
                }

            }
        }

        e.changeStartLocation(bestLocation);

    }

    /**
     * @return the number of human characters currently in the game
     */
    public int nbHumansAlive() {
        // Need to iterate through the list of characters
        // and count the number of humans
        int nbHumans = 0;
        for (Character character : characters) {
            if (character.getBreed().equals(Breed.HUMAN)) {
                nbHumans++;
            }
        }
        return nbHumans;
    }

    /**
     * @return the number of vampires characters currently in the game
     */
    public int nbVampiresAlive() {
        // Need to iterate through the list of characters
        // and count the number of humans
        int v = 0;
        for (Character character : characters) {
            if (character.getBreed().equals(Breed.VAMPIRE)) {
                v++;
            }
        }
        return v;
    }

    /**
     * @return the number of zombies characters currently in the game
     */
    public int nbZombiesAlive() {
        // Need to iterate through the list of characters
        // and count the number of humans
        int z = 0;
        for (Character character : characters) {
            if (character.getBreed().equals(Breed.ZOMBIE)) {
                z++;
            }
        }
        return z;
    }

    /**
     * Generate a pseudo-random boolean.
     * 
     * @return pseudo-random boolean
     */
    public static boolean GenerateRandomBoolean() {
        Random random = new Random();
        return random.nextBoolean();
    }

}
